/*

Copyright (c) 2021 唐佐林
WeChat : delphi_tang
EMail: delphi_tang@dt4sw.com

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/
#include "ipc_server.h"
#include "lwip/sockets.h"

#include <string.h> 
#include <malloc.h>
#include <unistd.h>
#include <stdio.h>

#define RPSVR_PORT 8899

static int g_Sock = -1;
static int g_Client = -1;
static struct sockaddr_in g_CAddr = {0};

static int HasClient()
{
    return (g_Client != -1);
}

static void CloseClient()
{
    if( HasClient() )
    {
        close(g_Client);
        g_Client = -1;
    }
}

int Server_Init(void)
{
    if( !Server_IsOk() )
    {
        g_Sock = socket(AF_INET, SOCK_STREAM, 0);
    }

    return (g_Sock != -1) ? 0 : -1;
}

int Server_Accept(void)
{
    int ret = -1;
    
    if( Server_IsOk() && !HasClient() )
    {
        size_t size = sizeof(g_CAddr);

        bzero(&g_CAddr, size);
        
        g_Client = accept(g_Sock, (struct sockaddr*)&g_CAddr, (socklen_t*)&size);
        
        ret = (g_Client != -1) ? 0 : -1;
    }
    
    return ret;
}

int Server_Start(void)
{
    static struct sockaddr_in s_Addr = {0};
    int ret = Server_IsOk() ? 0 : -1;

    if( ret == 0 )
    {
        bzero(&s_Addr, sizeof(s_Addr));

        s_Addr.sin_family = AF_INET;
        s_Addr.sin_addr.s_addr = htonl(INADDR_ANY);
        s_Addr.sin_port = htons(RPSVR_PORT);

        if( (ret = bind(g_Sock, (struct sockaddr *)&s_Addr, sizeof(s_Addr))) != -1 )
        {
            ret = listen(g_Sock, 1);
        }

        if( ret != 0 )
        {
            close(g_Sock);
            g_Sock = -1;
        }
    }

    return ret;
}

int Server_IsOk(void)
{
    return (g_Sock != -1);
}

char* Server_CAddr(void)
{
    char* ret = NULL;

    if( HasClient() )
    {
        ret = inet_ntoa(g_CAddr.sin_addr);
    }


    return ret;
}

int Server_Send(Message* msg)
{
    int err = (msg && HasClient()) ? 0 : -1;
    
    if( !err )
    {
        int size = sizeof(*msg) + msg->length;
        
        msg->type = htons(msg->type);
        msg->cmd = htons(msg->cmd);
        msg->index = htons(msg->index);
        msg->total = htons(msg->total);
        msg->length = htonl(msg->length);
        
        err = (send(g_Client, msg, size, 0) == size) ? 0 : -1;
        
        if( err )
        {
            CloseClient();
        }
    }
    
    return err;
}

static int ToRecv(char* buf, int size)
{    
    int retry = 0;
    int i = 0;
    
    while( i < size )
    {
        int len = recv(g_Client, buf + i, size - i, 0);
                
        if( len > 0 )
        {
            i += len;
        }
        else
        {
            if( retry++ > 5 )
            {
                break;
            }
                    
            usleep(200 * 1000);
        }
    }
    
    return (i == size);
}

Message* Server_Recv(void)
{
    Message* ret = NULL;
    unsigned char buf[sizeof(Message)] = {0};
    
    if( ToRecv(buf, sizeof(buf)) )
    {
        Message* msg = (Message*)buf;
        
        msg->type = ntohs(msg->type);
        msg->cmd = ntohs(msg->cmd);
        msg->index = ntohs(msg->index);
        msg->total = ntohs(msg->total);
        msg->length = ntohl(msg->length);
        
        ret = malloc(sizeof(Message) + msg->length);
        
        if( ret )
        {
            *ret = *msg;
            
            if( !ToRecv(ret->payload, ret->length) )
            {
                free(ret);
                ret = NULL;
                CloseClient();
            }
        }
    }
    else
    {
        CloseClient();
    }
    
    return ret;
}

void Server_Stop(void)
{
    CloseClient();
    
    if( Server_IsOk() )
    {
        close(g_Sock);
        g_Sock = -1;
    }
}
